package adke.managers;

import adke.Util;
import adke.gui.Overview;
import adke.gui.panels.GFXPanel2;
import adke.gui.panels.LinePanel;
import adke.listeners.GameListener;
import adke.models.LinePanelBorder;
import adke.models.Player;
import foxtrot.Task;
import foxtrot.Worker;
import java.awt.Color;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.event.KeyEvent;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;
import javax.swing.SwingUtilities;

import static java.awt.event.KeyEvent.*;

// manager van de game die gespeeld gaat worden, of nu bezig is

public class GameManager 
{
    private static Vector<GameListener> listeners;
    
    private static int currentRound;
    
    private static boolean paused;
    
    // de frequentie waarmee het tekenpaneel ververst wordt.
    // het tekenpaneel vraagt namelijk elke x seconden aan alle players wat hun coordinaten zijn
    // etc om ze te tekenen
    private static long frequency; // milliseconden
    
    // variabelen voor het spel die gecustomized kunnen worden
    
    // lineSpeed:   aantal pixels dat de lijn per frequentie vooruitgaat
    //              kan varieren door powerups
    private static double lineSpeed, turnSpeed;
    private static int lineThickness;
    private static int holeFrequency;
    private static int holeLength;
    
    private static int endScore;
    
    private static Timer repaintTimer;
    
    // geeft aan dat players maar één abilitypowerup tegelijk kunnen dragen
    private static boolean singleAbilityPowerup;
    
    // het effect van de powerup moet niet voorspeld kunnen worden door het uiterlijk
    private static boolean surprisePowerupEffects;
    
    private static boolean enablePowerups;
    
    private static boolean enableBorder;
    
    // houd de tijd bij ivm log
    private static long gameStartTime;
    
    public GameManager()
    {
        // TODO: uit settings halen
        frequency = 40;
        lineSpeed = 2;
        lineThickness = 4;
        turnSpeed = 30;
        holeFrequency = 400; // dus een gat laten vallen willekeurig binnen x repaints
        holeLength = 3;
        
        singleAbilityPowerup = true;
        surprisePowerupEffects = false;
        enablePowerups = true;
        enableBorder = true;
        
        // pas op: als de speed > lineThickness
        // dan vallen er gaten
        if (lineSpeed > lineThickness)
        {
            System.err.println("Speed > lineThickness! Line corruption may occur.");
        }
        
        paused = false;
        
        listeners = new Vector();
    }
    
    public static void addListener(GameListener listener)
    {
        if (!listeners.contains(listener))
            listeners.add(listener);
    }
    
    // in seconden
    public static int getGameTime()
    {
        long now = System.currentTimeMillis();
        long time = now - gameStartTime;
        return (int) (time / 1000); // delen door milliseconden
    }
    
    public static void startGame()
    {
        // met één player kunnen we nog wel een AI maken
        if (PlayerManager.getPlayerCount() == 0) return; // zonder spelers kunnen we niet spelen
        
        gameStartTime = System.currentTimeMillis();
        
        // doe er een AI bij
        if (PlayerManager.getPlayerCount() == 1)
        {
            
        }
        
        endScore = ScoreManager.getEndScoreForPlayerCount(PlayerManager.getPlayerCount());
        
        currentRound = 0;
        
        Overview.getInstance().showGamePanel();
        
        // notify listeners
        // de ScoreManager moet ervan genotified worden dat de game begint
        // vóórdat de ScoreManager een WallPlayer aanmaakt die vervolgens
        // aan deze array van listeners gevoegd wordt. Dat resulteert namelijk in een concurrentmodificationexception
        
        ScoreManager.getInstance().gameStarted();
        
        for (GameListener listener : listeners)
        {
            listener.gameStarted();
            
            // hier zouden we ook de concurrentmodificationexception kunnen vangen
        }
        
        nextRound();
    }
    
    // laat de scores zien
    public static void endGame()
    {        
        // notify listeners
        for (GameListener listener : listeners)
        {
            listener.gameEnded();
        }
        
        Overview.getInstance().showScores();
        
        // start een nieuwe Timer die over een korte periode naar de setup gaat
        Timer t = new Timer();
        
        // ga na 5 seconden naar setup
        // TODO: geef na 5 seconden de mogelijkheid op spatie te drukken
        TimerTask returner = new TimerTask()
        {
            public void run()
            {
                // reset spelers
                PlayerManager.reset();
                
                Overview.getInstance().showSetup();
            }
        };
        t.schedule(returner, 5000);
    }
    
    public static void pause()
    {
        paused = !paused;
    }
    
    // zet de ronde stil want er heeft iemand gewonnen
    public static void roundEnd()
    {
//        // notify listeners
        for (GameListener listener : listeners)
        {
            listener.roundEnded();
        }
        
        endTimer();
        Overview.setGameState(Overview.STATE_ROUND_FINISH);
        
        System.out.println("Round ended");
        System.out.println();
    }
    
    public static void nextRound()
    {
        System.out.println("Round started");
        currentRound++;
        Overview.setGameState(Overview.STATE_INGAME);
        
        // notify listeners
        for (GameListener listener : listeners)
        {
            listener.roundStarted();
        }
        
        // start de updater
        startTimer();
    }
    
    // start de timer die met de gegeven frequency het scherm repaint
    private static void startTimer()
    {
        prepare();
        
        // de taak die elke *frequency* wordt aangeroepen
        final TimerTask tt = new TimerTask()
        {
            public void run()
            {
                // reken volgende posities uit
                PlayerManager.doNextSteps();

                // kijkt welke gebieden er veranderd zijn
                // tekent die gebieden
                LinePanel.getInstance().repaint();
                
                // nadat de basisonderdelen getekend zijn tekenen we de animaties
                AnimationManager.runAnimations();
                
                // we bepalen dit deels door te kijken naar wat er getekend is
                validatePlayers();
            }
        };
        
        repaintTimer = new Timer();
        repaintTimer.scheduleAtFixedRate(tt, 0, frequency);   
    }
    
    // timer die ervoor zorgt dat het spel niet meteen begint
    // maar dat je steeds ziet waar de punten worden neergezet
    private static void prepare()
    {
        GFXPanel2.getInstance().reset();
        
        // doe dit in een nieuwe thread, zodat de EDT wordt vrijgehouden
        // om te wisselen naar de gamepanel
        // maar het mooie van FoxTrot is dat de thread pas returned alsi klaar is
        try
        {
            Worker.post(new Task()
            {
                public Object run() throws Exception
                {
                    // de tijd die we wachten voordat we uberhaupt punten op het scherm tekenen
                    try
                    {
                        Thread.sleep(1000);
                    } 
                    catch (InterruptedException ex)
                    {
                        ex.printStackTrace();
                    }
                    
                    // haal de players op
                    Player[] players = PlayerManager.getPlayers();
                    
                    // voor iedere player
                    for (Player p : players)
                    {
                        // teken de lijn op de buffer (is nu nog een stip)
                        p.getLine().paint();

                        // de tijd die we wachten tussen iedere lijn die we tekenen
                        try
                        {
                            Thread.sleep(1000);
                        } 
                        catch (InterruptedException ex)
                        {
                            ex.printStackTrace();
                        }
                    }
                    
                    return null;
                }
            });
        } 
        catch (Exception x) 
        {
            x.printStackTrace();
        }
    }
    
    private static void endTimer()
    {
        // er wordt niet meer getekend
        repaintTimer.cancel();
    }
    
    // kijk welke spelers nu af zijn gegaan
    private static void validatePlayers()
    {
        int killCount = 0;
        
        // kijk eerst wie er buiten de randen is
        
        Player[] players = PlayerManager.getPlayers();
        
        for (Player p : players)
        {
            // dode spelers hoeven we niet te checken
            if (p.isAlive())
            {
                Point pos = p.getLine().getCurrentPosition();
                
                // als de border enabled is, kijk eerst of er spelers buiten de randen komen
                if (isBorderEnabled())
                {
                    if (!LinePanelBorder.getInstance().pointWithinBorder(pos))
                    {
                        p.setAlive(false);
                        killCount++;
                    }
                }
                
                // kijk of het huidige punt van de speler wel in het veld ligt
                if (!LinePanel.getInstance().pointIsInField(pos) && p.isAlive())
                {
                    // als we een border hebben zou dit niet mogen gebeuren
                    if (isBorderEnabled())
                        System.err.println("Player got out of bounds while border is enabled!");
                    
                    // dag player
                    p.setAlive(false);
                    killCount++;
                }
            }
            else
            {
                killCount++;
            }
        }
        
        // hebben we al een winnaar?
        if (killCount == PlayerManager.getPlayerCount() - 1)
        {
            roundEnd();
            return;
        }
        
        // als we tot hier komen moeten we checken op coalisies (dammit)
        
        for (Player p : players)
        {
            if (p.isAlive())
            {
                // het middelpunt van de kop van de slang
                Point pos = p.getLine().getCurrentPosition();
                
                // het punt in lijn vóór de lijn of slang dat we testen om te kijken of we ergens tegenaan rijden
                Point[] headingPoint = LinePanel.getInstance().getHeadingPoint(pos, lineThickness, p.getLine().getCurrentDirection(), lineSpeed);
                        
                // als deze player afstevent op zijn dood
                if (!LinePanel.getInstance().headingToEmptySpace(headingPoint))
                {
                    // kijk door wat hij is doodgegaan
                    Color killerColor = LinePanel.getInstance().getColorInField(headingPoint);
                    
                    // de speler door wie deze speler dood is
                    Player killer = PlayerManager.getPlayer(killerColor);

                    // BUG
                    // als er geen speler is met deze kleur gaat het op een gegeven moment fout
                    if (killer == null)
                    {
                        System.err.println("");
                        System.err.println("While validating deaths:");
                        System.err.println("BUG: No player found with color: " + killerColor);

                        // WORKAROUND
                        // vergelijk van elke speler die we langsgaan de kleur met de gevonden kleur
                        // zet de verschilscore in een array en de speler met de laagste verschilscore
                        // moet dan wel de killer zijn (sort cluedo) 
                        Player bestMatch = null;
                        int matchValue = 9999;

                        // voor de debuginfo, print even alle spelernamen en hun kleuren uit
                        for (Player p2 : players) // ga voor de tweede keer door de array
                        {
                            System.err.println(p2.getName() + ": " + p2.getColor());

                            // vergelijk
                            int value = Util.getColorRelativity(killerColor, p2.getColor());

                            // als deze vergelijking beter is dan de vorige
                            if (value < matchValue) 
                            {
                                matchValue = value;
                                bestMatch = p2;
                            }
                        }

                        System.err.println("We guess " + bestMatch + " must be the killer");

                        // gok op deze
                        p.killedBy(bestMatch);

                        System.err.println("");
                    }

                    else
                    {
                        p.killedBy(killer);
                    }
                    
                    killCount++;
                }
            }
        }
        
        // hebben we al een winnaar?
        if (killCount == PlayerManager.getPlayerCount() - 1)
        {
            roundEnd();
            return;
        }
    }
    
    public static double getLineSpeed()
    {
        return lineSpeed;
    }
    
    public static double getTurnSpeed()
    {
        return turnSpeed;
    }
    
    public static int getLineThickness()
    {
        return lineThickness;
    }

    public static int getHoleFrequency()
    {
        return holeFrequency;
    }
    
    public static int getHoleLength()
    {
        return holeLength;
    }
    
    public static int getEndScore()
    {
        return endScore;
    }
        
    //
    
    public static boolean isPowerupsEnabled()
    {
        return enablePowerups;
    }
    
    public static boolean isBorderEnabled()
    {
        return enableBorder;
    }
    
    // kunnen we aan de blokjes zien hoe het de speler gaat beinvloeden?
    public static boolean getPowerupForeseeableFX()
    {
        return !surprisePowerupEffects;
    }
}
